/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "readcommand.hh"
#include "../../util/util.hh"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#include <readline/readline.h>
#include <readline/history.h>

#define HISTORY_FILE       "readcommand_history.txt"
#define PROMPT             "rc> "
#define N_COMMAND_FIFO     2 // must be 2
#define MASK_COMMAND_FIFO  (N_COMMAND_FIFO-1)

static readcommand *readcommand_singleton=NULL;
static const char *readcommand_empty_string="";

CLASS_INIT(readcommand); // READCOMMAND CLASS

readcommand::readcommand()
{
    CLASS_CTOR(readcommand);
    ctor();
}

readcommand::~readcommand()
{
    CLASS_DTOR(readcommand);
    dtor();
}

void readcommand::ctor()
{
    // init variables
    thread=NULL;
    thread_force_end=0;

    command_fifo=(char **)realloc(NULL, N_COMMAND_FIFO*sizeof(char*));
    memset(command_fifo, 0, N_COMMAND_FIFO*sizeof(char*));
    cf_set=cf_get=0;

    executing=0;

    completion_user=0;
    completion_user_data=NULL;

    save_history=1;

    // prompt and history file
    prompt=history_file=NULL;
}

void readcommand::dtor()
{
    kill_thread();
    cf_set=cf_get=0;

    if(command_fifo)
    {
	for(int i=0; i<N_COMMAND_FIFO; i++) if(command_fifo[i]) free(command_fifo[i]);
	command_fifo=(char**)realloc(command_fifo, 0);
    }

    if(prompt) prompt=(char *)realloc(prompt, 0);
    if(history_file) history_file=(char *)realloc(history_file, 0);
}

int readcommand::init(const char *prompt_, const char *history_file_)
{
    // free previous prompt and history_file
    if(prompt) free(prompt);
    if(history_file) free(history_file);

    set_prompt(prompt_);
    if(history_file_) history_file=strdup(history_file_); else history_file=strdup(HISTORY_FILE);

    if(!thread)
    {
	thread_force_end=0;
	cf_set=cf_get=0;
	executing=0;

	pthread_attr_t th_attr;
	thread=(pthread_t *)realloc(thread, sizeof(pthread_t));

	if(pthread_attr_init(&th_attr) ||
	   pthread_attr_setdetachstate(&th_attr, PTHREAD_CREATE_JOINABLE) ||
	   pthread_create(thread, &th_attr, (void *(*)(void*))thread_main_static, this))
	{
	    ERROR("pthread_create %s", strerror(errno));
	    thread=(pthread_t *)realloc(thread, 0);
	    return 0;
	}
    }
    return 1;
}

void readcommand::set_prompt(const char *prompt_)
{
    if(!prompt_) {prompt=strdup(PROMPT); return;}

    // count escape sequences
    int i, j, n_esc=0;
    for(i=0; prompt_[i]; i++) if(prompt_[i]==27) n_esc++;

    // escape prompt with readline ignores
    prompt=(char*)malloc(i+(n_esc<<1)+1);
    n_esc=0;
    for(i=0, j=0; prompt_[i]; i++)
    {
	if(prompt_[i]==27)
	{
	    prompt[j++]=RL_PROMPT_START_IGNORE;
	    n_esc++;
	}
	prompt[j++]=prompt_[i];
	if(n_esc && prompt_[i]=='m')
	{
	    n_esc=0;
	    prompt[j++]=RL_PROMPT_END_IGNORE;
	}
    }
    prompt[j]=0;
}

void readcommand::kill_thread()
{
    if(thread)
    {
	DEBUG("waiting readcommand thread to end");
	thread_force_end=1;
	if(pthread_join(*thread, NULL)) ERROR("thread_join %s", strerror(errno));
	thread=(pthread_t *)realloc(thread, 0);
    }
    thread_force_end=0;
}

void *readcommand::thread_main_static(readcommand *r)
{
    return r->thread_main();
}

int readcommand::event_hook_static()
{
    return readcommand_singleton->event_hook();
}

int readcommand::event_hook()
{
    // refresh something here if necesary


    // end variables
    if(thread_force_end)
    {
	rl_line_buffer[0]=0;
	rl_done=1;
    }
    return 0;
}

char *readcommand::from_completion_user_provided_generator(const char *text, int state)
{
    return readcommand_singleton->completion_user_provided[state];
}

char **readcommand::completion_static(const char *text, int start, int end)
{
    return readcommand_singleton->completion(text, start, end);
}

char **readcommand::completion(const char *text, int start, int end)
{
    if(completion_user && (completion_user_provided=completion_user(text, start, end, rl_line_buffer, completion_user_data)))
    {
	rl_completion_append_character=0;
	char **dev=rl_completion_matches(text, from_completion_user_provided_generator);
	free(completion_user_provided);
	return dev;
    }

    // do internal completion if any

    return NULL;
}

int readcommand::getc_func(FILE *f)
{
    // this solves a problem with reverse search escape secuences
    static int search_esc=0;
    int dev=rl_getc(f);
    if(RL_ISSTATE(RL_STATE_ISEARCH))
    {
	if(dev==27) search_esc=1;
	if(search_esc) dev=27;
    }
    else search_esc=0;
    return dev;
}

void *readcommand::thread_main()
{
    if(readcommand_singleton)
    {
	thread_force_end=1;
	ERROR_R0("internal singleton violation");
    }

    // readline history file load
    int res;
    if((res=read_history(history_file))) ;//ERROR("read_history %s", strerror(res));

    readcommand_singleton=this;
    rl_event_hook=event_hook_static;
    rl_attempted_completion_function=completion_static;
    rl_getc_function=getc_func;

    char *command=NULL;
    while(!thread_force_end && (command=readline(prompt)))
    {
	if(*command)
	{
	    add_history(command);

	    if(command_fifo[cf_set]) free(command_fifo[cf_set]);
	    command_fifo[cf_set]=command;
	    command=NULL;
	    cf_set=(cf_set+1)&MASK_COMMAND_FIFO;

	    while(((cf_set+1)&MASK_COMMAND_FIFO)==cf_get && !thread_force_end) usleep(1000);
	}
	else command=(char*)realloc(command, 0);
    }

    if(save_history && (res=write_history(history_file))) ERROR("write_history %s", strerror(res));
    clear_history();

    readcommand_singleton=NULL;

    if(command) free(command);
    if(!thread_force_end)
    {
	thread_force_end=1;
	DEBUG("command insertion thread closed");
	printf("exit\n");
    }
    return NULL;
}

char *readcommand::command()
{
    if(!thread) return NULL;
    if(thread_force_end)
    {
	kill_thread();
	return NULL;
    }

    if(executing)
    {
	cf_get=(cf_get+1)&MASK_COMMAND_FIFO;
	executing=0;
    }

    if(cf_set==cf_get) return (char*)readcommand_empty_string;
    else
    {
	char *command=command_fifo[cf_get];
	executing=1;
	return command;
    }
}

int readcommand::set_completion(char **(*comp)(const char *text, int start, int end, char *complete_text, void *data), void *comp_data)
{
    completion_user=comp;
    completion_user_data=comp_data;
    return 1;
}

// COMMAND LIST
command_struct *readcommand::command_list=NULL;
void *readcommand::command_list_ctx=NULL;

char **readcommand::command_list_completion(const char *text, int start, int end, char *complete_text, void *obj)
{
    // count
    int len=end-start, i=0, n_list=0;
    while(command_list[i].name) if(!strncmp(text, command_list[i++].name, len)) n_list++;

    // create list
    char **comp=(char**)malloc((n_list+1)*sizeof(char*));
    i=n_list=0;
    while(command_list[i].name)
	if(!strncmp(text, command_list[i++].name, len))
	{
	    int n_name=strlen(command_list[i-1].name);
	    char *name=(char*)malloc(n_name+2);
	    snprintf(name, n_name+2, "%s ", command_list[i-1].name);
	    comp[n_list++]=name;
	}
    comp[n_list]=NULL;
    return comp;
}

int readcommand::command_list_start(command_struct *command_list_, void *ctx)
{
    command_list=command_list_;
    command_list_ctx=ctx;
    if(!set_completion(command_list_completion, NULL)) return 0; else return 1;
}

int readcommand::command_list_execution(char *command_user)
{
    static char **split=NULL;
    static int n_split=0, n_split_max=0;

    char *com, *splitted, *splitted_tmp;
    if((com=command_user) || (com=command()))
    {
	if(*com)
	{
	    // parser
	    splitted=strdup(com);
	    splitted_tmp=splitted;
	    n_split=0;
	    do
	    {
		while(*splitted_tmp==' ') {*splitted_tmp=0; splitted_tmp++;}
		if(!*splitted_tmp) break;
		n_split++;
		if(n_split>n_split_max)
		{
		    n_split_max+=16;
		    split=(char **)realloc(split, (n_split_max+1)*sizeof(char *));
		}
		split[n_split-1]=splitted_tmp;
		splitted_tmp=strchr(splitted_tmp, ' ');
	    }
	    while(splitted_tmp);

	    // call to function
	    int i=0;
	    while(command_list[i].name)
	    {
		if(!strcmp(split[0], command_list[i].name))
		{
		    int dev;
		    switch((dev=command_list[i].function(n_split, split, command_list_ctx)))
		    {
		    case -1: printf("internal command \"%s\" failed\n", com); break; // error
		    case -2: break; // ok
		    case -3: // show help
			split[1]=split[0]; split[0]=const_cast<char*>("help");
			command_list_help(2, split, command_list_ctx); break;
		    }
		    break;
		}
		i++;
	    }
	    if(!command_list[i].name) printf("internal command not found\n");
	    free(splitted);
	}
    }
    else return 0;
    return 1;
}

int readcommand::command_list_help(int n_param, char **param, void *ctx)
{
    int i=0, j=0;
    while(command_list[i].name)
    {
	if(n_param==1 || !strcmp(param[1], command_list[i].name))
	{
	    printf(command_list[i].proto, command_list[i].name);
	    printf("\n");
	    j++;
	}
	i++;
    }
    if(!j)
    {
	printf("help on %s not available\n", param[1]);
	return -1;
    }
    return -2;
}
